package gbench.appdemo.mall.erp;

import static gbench.common.matlib.MatlibCanvas.println;
import static gbench.common.tree.LittleTree.IRecord.REC;
import static gbench.common.tree.LittleTree.KVPair.KVP;
import static gbench.common.tree.LittleTree.Term.FT;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Stream;

import gbench.appdemo.mall.erp.components.ICredit;
import gbench.appdemo.mall.erp.components.IMoney;
import gbench.appdemo.mall.erp.components.IPrice;
import gbench.common.tree.LittleTree.IRecord;

/**
 * 金融模块
 * @author gbench
 *
 */
public class FinanceApp {
    
    /**
     * 货币
     * 
     * @author Administrator
     *
     */
    public class Money extends Entity<Money> implements ICredit<IMoney>, IMoney {
        
        /**
         * 货币对象
         * 
         * @param id   货币id
         */
        public Money(final Integer id) {
            this.id = id;
        }

        /**
         * 货币对象
         * 
         * @param id   货币id
         * @param name 货币名称
         */
        public Money(final Integer id, final String name) {
            this.id = id;
            this.name = name;
        }
        
        /**
         * 对当前的货币 采用 money 进行计价。
         * 
         * @param money 计价的货币
         * @return Price(ratio,money)
         */
        
        @Override
        public <U> Double ratioOf(final ICredit<U> money) {
            final var p0 = this.attributes.dbl("price"); // 相对于本位币的价格
            final var p1 = money.getAtrributes().dbl("price"); // 相对于本位币的价格
            final var ratio = p0 / p1;
            return ratio;
        }

        /**
         * 对当前的货币 采用 money 进行计价。
         * 
         * @param money 计价的货币
         * @return Price(ratio,money)
         */
        @Override
        public IPrice priceOf(final ICredit<IMoney> money) {
            final var ratio = this.ratioOf(money);
            return new Price(ratio, (IMoney) money);
        }

        /**
         * 是否是本位币
         * 
         * @return 本位币,true,other false
         */
        public boolean isStandard() {
            return this.getAttributes().dbl("flag") > 0;
        }

        @Override
        public Double getValue() {
            return 1d;
        }

        @Override
        public Money getUnderlying() {
            return this;
        }

        @Override
        public IRecord getAtrributes() {
            return this.attributes;
        }
        
        /**
         * hashCode
         */
        public int hashCode() {
            return Objects.hash(this.id,this.name,this.attributes);
        }
        
        /**
         * 等价判断
         */
        public boolean equals(final Object obj) {
            if (!(obj instanceof Money)) return false;
            final var money = (Money) obj;
            return this.id.equals(money.getId());
        }
    }

    /**
     * 静态概念：价格对象<br>
     * 动态概念：计价对象(this_price),用于对另外一个价格对象another_price 采用 <br>
     * this_price 的货币this_money进行计价。this_price.getPrice(another_price);
     * 
     * @author Administrator
     *
     */
    public class Price implements IPrice {
        
        /**
         * 生成一个计价对象
         * 
         * @param amount 数量
         * @param money  计价货币
         */
        public Price(final Number amount, final IMoney money) {
            this.amount = amount.doubleValue();
            this.money = money;
        }
        
        /**
         * 金额对象
         * 
         * @return 提取金额对象
         */
        public Double getAmount() {
            return this.amount;
        }

        /**
         * 货币
         * 
         * @return 提取货币对象
         */
        public IMoney getMoney() {
            return this.money;
        }
        
        /**
         * 把 another_price 对象采用 本价格对象(this)的 计价货币进行重新计价。<br>
         * 注意 another_price对象 和 本价格对象(this) 是两个对象, 他们可以分别采用 不同的货币 进行计价。
         * 
         * @param another_price 待计价的 价格对象
         * @return 把 another_price对象采用 本价格对象(this)的 计价货币进行重新计价。
         */
        public IPrice reprice(final IPrice another_price) {
            final var this_money = this.getMoney();
            @SuppressWarnings("unchecked")
            final var another_price_money_of_this_price = another_price.getMoney().priceOf((ICredit<IMoney>)this_money); // price 的 this_money的价格
            return another_price_money_of_this_price.mul(another_price.getAmount()); // 把 another_price对象采用 本价格对象(this)的 计价货币进行重新计价。
        }
        
        /**
         * reprice 的别名 <br>
         * 把 another_price 对象采用 本价格对象(this)的 计价货币进行重新计价。<br>
         * 注意 another_price对象 和 本价格对象(this) 是两个对象, 他们可以分别采用 不同的货币 进行计价。
         * 
         * @param another_price 待计价的 价格对象
         * @return 把 another_price对象采用 本价格对象(this)的 计价货币进行重新计价。
         */
        public IPrice getPrice(final IPrice another_price) {
            return this.reprice(another_price);
        }
        
        /**
         * 对当前price采用货币money重新计价
         * @param money 计价货币
         * @return 货币重新计价
         */
        public IPrice priceOf(final IMoney money) {
            return new Price(1,money).getPrice(this);
        }
        
        /**
         * 价格乘法
         * @param price 价格数量
         * @return 价格
         */
        public IPrice mul(final Number price) {
            final var _amount = this.amount*price.doubleValue();
            return new Price(_amount,this.money);
        }
        
        /**
         * 价格乘法
         * @param price 价格对象
         * @return 价格
         */
        public IPrice mul(final IPrice price) {
            final var _amount = this.amount*this.getPrice(price).getAmount();
            return new Price(_amount,this.money);
        }
        
        /**
         * 价格加法
         * @param price 价格数量
         * @return 价格
         */
        public Price plus(final Number price) {
            final var _amount = this.amount + price.doubleValue();
            return new Price(_amount, this.money);
        }
        
        /**
         * 价格加法
         * @param price 价格数量
         * @return 价格
         */
        public Price plus(final Price price) {
            final var _amount = this.amount+this.getPrice(price).getAmount();
            return new Price(_amount,this.money);
        }
        
        /**
         * 价格加法
         * @param price 价格数量
         * @return 价格
         */
        public Price minus(final Price price) {
            final var _amount = this.amount-this.getPrice(price).getAmount();
            return new Price(_amount,this.money);
        }
        
        /**
         * 价格加法
         * @param price 价格数量
         * @return 采用price作为计价货币的产品价格。
         */
        public Price divide(final Price price) {
            final var _amount = this.amount / this.getPrice(price).getAmount();
            final var finance = FinanceApp.this; // 金融模块
            final var new_money_name = FT("{新货币-[$0*$1]-$2}", price.amount, price.getMoney(), UUID.randomUUID());
            final var new_price = price.priceOf(finance.getStandardMoney()).getAmount(); // 提取交易比率
            return new Price(_amount, finance.createMoney(REC("name", new_money_name, "price", new_price)));
        }
        
        /**
         * 
         */
        public String toString() {
            return FT("$0 $1",this.money,this.amount);
        }

        private Double amount;
        private IMoney money;
    }
    
    /**
     * MyERPApp 的初始化
     */
    public void initialize(final Stream<IRecord> moneyList) {
        println("初始化货币环境");
        final var moneys = moneyList // 提取系统货币
            .map(e -> {
                final var money = new Money(e.i4("id"), e.str("name"));
                final var kvp = KVP(e.str("name"), money);
                money.setAttributes(e); // 为对象增加属性。
                return kvp; // 属性对儿
            }).collect(IRecord.rclc2);
        
        // 注册货币
        moneys.foreach((String symbol,Money money)->{
            this.registMoney(money);
            if(money.isStandard()) this.standardMoney = money; // 设置本位币
            println(money.isStandard()?"*":"-",money,money.getId());
        });
        this.moneyIdPool.set(moneyRegistry.size()+1);
        
        println("-------------------------------------------------------------------------");
        println("汇率换算");
        final var USD = this.USD();
        final var CNY = this.CNY();
        final var JPY = this.MONEY("日元");
        println("USD/CNY",USD.priceOf(CNY),CNY);
        println("CNY/USD",CNY.priceOf(USD),USD);
        println("JPY/CNY",JPY.priceOf(CNY),CNY);
        println("USD/JPY",USD.priceOf(JPY),JPY);
        println("10CNY+10USD:",this.rmb(10).plus(this.dollar(10)),"CNY");
        println("10USD+10CNY:",this.dollar(10).plus(this.rmb(10)),"USD");
        println("10JPY+10USD:",this.price(10, JPY).plus(this.dollar(10)),"JPY");
        final var dollar100 = this.price(100, this.USD()); // 100美元
        final var rmb10 = this.price(10,this.CNY()); // 10 元的人民币
        final var new_price = dollar100.divide(rmb10); // 新价格
        println("-------------------------------------------------------------------------");
        println("dollar100以rmb10进行计价",new_price,
                "\n新货币转换到美元",new_price.priceOf(this.USD()),
                "\n新货币转换到人民币",new_price.priceOf(this.CNY()),
                "\n新货币转换到日元",new_price.priceOf(this.MONEY("日元"))
        );// println
        println("-------------------------------------------------------------------------");
    }
    
    /**
     * 注册货币
     * @param money
     */
    public void registMoney(IMoney money) {
        moneyRegistry.set(money.getName(),money);
        moneyTable.put(money.getId(), money);
    }
    
    /**
     * 人民币计价
     * @param amount 数量值
     * @return
     */
    public Price rmb(final Number amount) {
        return this.price(amount.doubleValue(),this.CNY());
    }
    
    /**
     * 美元计价
     * @param amount 数量值
     * @return
     */
    public Price dollar(final Number amount) {
        return this.price(amount.doubleValue(),this.USD());
    }
    
    /**
     * 制造一个价格
     * @param amount
     * @param money
     * @return
     */
    public Price price(final Number amount, final IMoney money) {
        return new Price(amount.doubleValue(),money);
    }

    /**
     * 
     * @param id
     * @return
     */
    public IMoney getMoneyById(int id) {
        return this.moneyTable.get(id);
    }
    
    /**
     * 获取新的货币Id
     * @return 货币价格。
     */
    public Long getNextMoneyId() {
        return this.moneyIdPool.getAndIncrement();
    }
    
    /**
     * 创建一种货币
     * 
     * @param rec 货币信息:name 名称,price 与本位币的兑换比例,symbol 货币符号, label 货币简写,create_time 创建时间。
     * @return 货币对象。
     */
    public Money createMoney(final IRecord rec) {
        final var id = this.getNextMoneyId(); // 分配货币号
        final var name = rec.str("name");
        final var money = new Money(id.intValue(), name);
        money.setAttributes(rec);
        return money;
    }
    
    /**
     * 获得本位币
     * @return
     */
    public IMoney getStandardMoney() {
        return this.standardMoney;
    }
    
    /**
     * 
     * @return
     */
    public Money CNY() {
        return moneyRegistry.get("人民币",Money.class);
    }

    /**
     * 
     * @return
     */
    public Money USD() {
        return moneyRegistry.get("美元",Money.class);
    }

    /**
     * 提取货币
     * @return
     */
    public Money MONEY(String name) {
        return moneyRegistry.get(name,Money.class);
    }

    /**
     * 系统货币
     */
    public final IRecord moneyRegistry = REC(); // 系统的货币集合
    public final Map<Integer,IMoney> moneyTable = new HashMap<Integer,IMoney>(); // 系统的货币集合
    public AtomicLong moneyIdPool = new AtomicLong(0); // 货币ID池
    public IMoney standardMoney; // 货币ID池
    
}